package net.yuyoo.uusession.util;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 具有超时功能的并发hashMap
 * @author yuyoo
 *
 */
public class TimeoutConcurrentHashMap implements Map {

	private static final Log log = LogFactory.getLog(TimeoutConcurrentHashMap.class); 
	
	private static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
	
	private long schedule = -1;
	private Map cache = null;
	private Map lastTimeoutedCache = new ConcurrentHashMap(80);
	private List<TimeoutListener> listeners = new LinkedList<TimeoutListener>();
	
	private class ScheduleWorker implements Runnable {

		private ScheduledExecutorService executor = null;
		
		ScheduleWorker(ScheduledExecutorService executor) {
			this.executor = executor;
		}
		
		public void run() { 
			this.schedule();
			executor.schedule(this, TimeoutConcurrentHashMap.this.schedule, TimeUnit.MILLISECONDS);
		}	
		
		private void schedule() {
			
			TimeoutConcurrentHashMap.this.lastTimeoutedCache.clear();
			long now = System.currentTimeMillis();
			Map tar = TimeoutConcurrentHashMap.this.cache;
			Iterator it = tar.keySet().iterator();
			while (it.hasNext()) {
				Object key = it.next();
				Object value = tar.get(key);
				if (value instanceof Timeoutable) {
					Timeoutable t = (Timeoutable)value;
					if (t.isDelay()) {
						t.setTimeout(t.getTimeout() + t.getExpiringTime());
					}
					long timeout = t.getTimeout();
					if (timeout < now) {

						it.remove(); 
						
						TimeoutListener current = null;
						for (int i = 0; i < TimeoutConcurrentHashMap.this.listeners.size(); i++) {
							current = TimeoutConcurrentHashMap.this.listeners.get(i);
							value = current.handle(key, value);
						}
						TimeoutConcurrentHashMap.this.lastTimeoutedCache.put(key, value);
					}
				}
			}
		}
	}
	
	public TimeoutConcurrentHashMap(long schedule, int initialCapacity) {  

		if (schedule <= 0 || initialCapacity < 0 ) {
			throw new IllegalArgumentException();
		} 
		this.schedule = schedule;
		this.cache = new ConcurrentHashMap(initialCapacity);
		
		ScheduleWorker worker = new ScheduleWorker(executor);
		executor.schedule(worker, this.schedule, TimeUnit.MILLISECONDS);
	}
	
	public TimeoutConcurrentHashMap() {
		
		this(2 * 60 * 1000L, 50);
	}
	
	public void addTimeoutListner(TimeoutListener listener) {
		this.listeners.add(listener);
	}
	
	public TimeoutListener getTimeoutListner(int idx) {
		return this.listeners.get(idx);
	}
	
	public boolean removeTimeoutListener(TimeoutListener listener) {
		return this.listeners.remove(listener);
	} 
	
	public Map getLastTimeouted() {
		
		return this.lastTimeoutedCache;
	}

	public void clear() {

		this.cache.clear();
	}

	public boolean containsKey(Object key) { 
		
		return this.cache.containsKey(key); 
	}

	public boolean containsValue(Object value) {

		return this.cache.containsValue(value);
	}

	public Set entrySet() {

		return this.cache.entrySet();
	}

	public Object get(Object key) {

		Object obj = this.cache.get(key);
		if (obj instanceof Timeoutable) {
			Timeoutable tar = (Timeoutable) obj;
			if (tar.isDelay()) {
				tar.setTimeout(tar.getTimeout() + tar.getExpiringTime());
			}
		}
		return obj;
	}

	public boolean isEmpty() {
		return this.cache.isEmpty();
	}

	public Set keySet() {

		return this.cache.keySet();
	}

	public Object put(Object key, Object value) {
		
		return this.cache.put(key, value);
	}

	public void putAll(Map t) {
		
		this.cache.putAll(t);
	}

	public Object remove(Object key) {

		return this.cache.remove(key);
	}

	public int size() {

		return this.cache.size();
	}

	public Collection values() {
		
		return this.cache.values();
	}

}
